查看原文
其他

都 “2220” 年了,Web 前端测试就别“卷”了

前端大全 2023-03-21

The following article is from 字节前端 ByteFE Author 曹志浩

一. 前端测试是什么?

前端测试也是一种自动化测试技术,其测试的主要对象就是 Web 应用的图形用户界面(GUI)、功能和可用性,以确保 Web 应用的 GUI 层在连续的更新迭代中没有 Bug。

例如,可以检查输入字段是否接受正确的字符,表单是否仅在填写所需字段后才提交,导航是否足够简单,页面加载是否足够快,等等。

前端测试的目标是测试功能,并验证网站或应用程序的表示层是否存在错误或无错误。测试必须在每次系统更新或变更后执行,以确保最近的更改不会对 UI 层产生任何预期外的影响。

二. 为什么需要前端测试?

首先,前端与用户直接发生接触和交互,前端是用户操作与可见的一切。对于用户而言,前端也是他们唯一能感知到的存在,因此用户会把发生的一切问题都归因到前端。比如当接口返回的数据有问题,或许很难被用户发现;但只要有一个功能按钮点击出现预期外的反应,用户一定会发现并吐槽你的系统。

其次,日益复杂的平台功能、快速迭代的节奏,以及流动更加频繁的团队成员无疑都会为我们维护的前端系统带来更多的不确定性。

由此可见前端的功能或稳定性是多么重要,如何保证我们系统持续稳定的处于可用状态,这就是我们需要前端测试的意义。

三. 前端测试有哪些类型?

前端测试涵盖了各种策略,它涉及到一些在后端开发中已经流行多年的实践,也有一些前端背景下的特有需求;

下图是网上经常能看到的一张描述前端测试金字塔模型的示意图

  1. 单元测试 / Unit Testing

在开发领域最常见的测试类型之一,无论是前端测试还是服务端测试,它都是简单、直接、有效的一种测试方式;

前端单元测试通过验证 Web 应用的最小可能模块或单元的功能,并能做到与其他模块独立、分隔;它们或许是与特定函数或组件的小交互,负责在引入新更改时保持我们的代码正常运行,单元测试期望从组件/函数收到特定的响应,至少应该测试覆盖前端应用的核心功能。

前端单测如何跑起来?

在现代前端开发环境中,有各种各样的库让开发人员可以轻松地进行测试。其中一些最流行的是Jest[1]Jasmine[2]。它们具有内置方法,核心是“expect”,作用于方法/组件并检查输出是否是我们期望的结果。

这里举一个 React 官网给出的例子:

// hello.js

import React from "react";

export default function Hello(props) {
  if (props.name) {
    return <h1>你好,{props.name}!</h1>;
  } else {
    return <span>嘿,陌生人</span>;
  }
}
// hello.test.js

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";

import Hello from "./hello";

let container = null;
beforeEach(() => {
  // 创建一个 DOM 元素作为渲染目标
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  // 退出时进行清理
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

it("渲染有或无名称", () => {
  act(() => {
    render(<Hello />, container);
  });
  expect(container.textContent).toBe("嘿,陌生人");

  act(() => {
    render(<Hello name="Jenny" />, container);
  });
  expect(container.textContent).toBe("你好,Jenny!");

  act(() => {
    render(<Hello name="Margaret" />, container);
  });
  expect(container.textContent).toBe("你好,Margaret!");
});

什么时候写单测?

每个开发者都有自己测试相关的偏好,每个团队也都有自己的测试软件指南和要求。

TDD 的方式建议事先编写测试,然后实现代码。但是这种方法更适合于几乎把所有内容都写在技术概述中的软件开发场景,在互联网开发场景中更敏捷的迭代要求下似乎不是很合适。

因此在许多情况下,建议在过程中编写技术方案代码时同时编写测试方法。

这意味着在进行测试之前需要编写方法对应的测试代码,否则你最终会得到一个包含许多方法或依赖项的长代码。当你开始准备测试时,它可能会变成一个非常头痛的问题,甚至会产生放弃编写测试代码的想法。

  1. 集成测试 /Integration Testing

集成测试即就是组合不同的单元并将它们作为一个组进行测试。通常单个单元运行良好,但是它们在集成后又可能出现无法正常交互的情况。

例如,一个下拉列表组件在集成到导航栏组件后可能会无法正常的展示,而集成测试会暴露合并代码后出现的各种错误。

对组件来说,“单元测试”和“集成测试”之间的差别可能会很模糊。如果你在测试一个表单,用例是否应该也测试表单里的按钮呢?一个按钮组件又需不需要有他自己的测试套件?重构按钮组件是否应该影响表单的测试用例?

因为集成测试处在单元测试和端到端测试的夹击中,大多数时候我们并不会拿出精力来照顾集成测试,而是用单元测试或端到端测试来覆盖其场景;

  1. 端到端测试

    /End-To-EndTesting

理论上端到端测试是从头到尾测试我们 Web 应用的过程,因为它将模拟实际用户使用时的过程和操作。对于 Web 应用程序,它将启动浏览器,导航到正确的 URL,按预期操作路径使用功能,并验证行为。

无论是什么平台,想要正常运行应用都有一些依赖项,如:接口服务、数据指标、外部服务、日志记录等。通常我们可以在 BOE 环境进行测试,与正式生产环境分开。但在理想情况下,这个测试环境尽可能地代表正式生产环境。

优点与缺点

优点:

  • 即它更靠近于前端测试的金字塔顶端,因此更接近实际用户,更能代表真实用户在交互和操作时的实际体验;

缺点:

  • 很慢;因为它们需要遍历多个系统并且通常必须串行运行,所以每个测试可能需要几秒钟到几分钟才能完成。随着更多的代码进行 E2E 测试,事情会变得更加复杂,测试框架的运行速度呈指数级下降,甚至导致发布在 Pipeline 中堵塞。
  • 很难维护;端到端测试要求所有系统在运行前处于正确的状态,包括正确的版本和数据。比如服务端的测试环境 down 了,我们的 E2E 测试也会因为网络异常将执行失败;

  • 不可靠;由于测试环境的复杂性,以及过多的依赖项;它们经常会失败,导致误报。在许多情况下,可能都是一些代码问题外的原因。误报过多,召回率低,导致团队越来越不重视,最终也渐渐地不再维护。

  • 很难定位/修复;当 E2E 测试失败时,调试问题或定位问题通常很困难。因为无法直接定位到具体的代码片段问题,仍需要人工复现逐步排查,但又因为其不可靠也许根本无法复现。

  • 发现错误的过程较晚;不像单测可以在开发阶段就能执行,由于运行 E2E 测试的复杂性,大多数情况下这些测试仅在代码提交后在 CI/CD 流程中打包、部署、运行后才能执行,这种反馈延迟对现代敏捷交付团队来说代价高昂。

  • 副作用;因为 E2E 测试应该尽可能在贴近真实生产的环境中执行,其不可避免的会产生众多的副作用,如创建、更新、删除等操作,都会真实的生效和作用在我们的系统中;虽然在 BOE 环境不用过多关心影响,或者通过代理或请求过滤来避免副作用,不可否认的是它会进一步提升系统的复杂性和不可控;

适用场景

当看到仅有的优点和众多缺点时,也许你直接放弃了 E2E 测试的想法,但其存在就必定会有它的意义;

虽然对于公司内众多的中台或管理后台场景确实不适用,但它可以很好的满足组件库、官网、落地页等场景对于功能稳定可用的诉求,它非常适合于前后端交互/依赖少,无副作用产生的前端场景;

比如可以通过一个前端基座来加载组件库并渲染出预置的组件 Demo 样例,基于此进行 E2E 测试对组件进行交互验证其功能;那组件库其实就是唯一的变量和依赖项,我们可以很稳定的模拟用户对于组件的操作;此时不用担心系统依赖过多导致的不可靠和不可维护、难定位等问题,同时它也不会对系统产生任何的副作用,甚至我们都不需要关注登陆/权限的问题,可谓是一劳永逸;

  1. 验收测试/Acceptance Testing

验收测试需要确保指定的操作、用户输入和用户行为流是可完成的和正常运行的,并产生了符合预期的行为结果。

如果我们要实现一个计算器,仅仅因为代码中的 addNumbers 函数返回正确的数字,并不意味着计算器界面肯定会按预期运行以给出正确的结果。如果按钮被禁用,或者计算结果没有显示怎么办?验收测试即帮助我们解决这种问题;

说起来它也是夹在单元测试与 E2E 测试中的一种测试方式,到底怎么理解呢?就我个人的理解,验收测试其实就是相当于 QA 同学所进行的工作,通过阅读产品同学的 PRD 文档,拆解成一个又一个的用户故事,并写出测试用例来挨个验证研发交付的功能;

简言之,验收测试即就是产品逻辑的测试,验证一个又一个产品逻辑是否符合预期;比如:当用户登录时调整至 A 页面,如果用户未登录则弹窗提示;点击后跳转和点击后弹窗等功能都可以作为按钮组件单测用例的覆盖范围。但是什么时候跳转,什么时候弹窗则需要一个又一个明确的用户故事来驱动和测试;

  1. 视觉回归测试/Visual

    Regression Testing

视觉回归测试可以说是前端领域独有的一种测试方式,它不是对编写的代码或功能进行测试,而是将代码的渲染结果与系统在生产、暂存或预更改的本地环境中的渲染版本进行比较。

这种测试通常是通过比较在无头浏览器中截取的屏幕截图来完成的,图像比较工具来检测两个截图之间的些许差异。

与验收测试和单元测试不同,它对于新的特性/内容没有什么测试的收益,因为它需要有一个原始版本截图用于对比。如果你的系统 UI 将在整个开发过程中经常有比较大的变化,你可能就需要将这些视觉回归测试的用例保存下来,以便在回归界面的视觉部分来检测和使用。

如果视觉回归或视觉的一致性是你们系统中的一个痛点,那么与迭代后需要 QA/UED 频繁回归相比,这种方法可能会为团队节省时间和精力。

  1. 可访问性测试/Accessibility

    Testing

可访问性测试是为了确保每个人都可以访问应用程序,这主要是为了我们的系统能更好的服务有听力或视力障碍的用户,可访问性测试主要涉及检查我们系统/APP 是否与屏幕阅读器等设备兼容。

例如我们公司的 APP 都需要很好的遵守相关规范,以保证为不同人群都能提供良好的体验,那可访问性测试也许就是能保证这个场景稳定可用的方式之一。

  1. 性能测试/Performance

    Testing

性能测试很好理解,就是为了去验证我们的应用/页面的性能问题,对于客户端来说更为重要,需要去验证在各种机型/设备上的启动速度,视频播放过程中的性能甚至是耗电情况等。

对于 Web 端的性能测试而言会简单很多,参考 Lighthouse 的打分规则,通常关注以下几个常见的核心指标:

  1. 跨浏览器兼容性测试/

    Cross-Browser

    Compatibility Testing

跨浏览器兼容性测试,也是一种前端测试特有的类型。它侧重于使用户能够在不同的浏览器上获得相同的体验,在一个浏览器上可用的功能也应该在其他浏览器上可用。它还应确保应用程序在不同的操作系统、设备和浏览器组合上正常工作。

因为移动端的出现,目前 PC 的 Web 站使用率已大大降低,开发中也基本上不需要开发者为 IE 浏览器而兼容和买单,因此其跨浏览器兼容测试的必要性也大大降低了。

如果是公司内的中后台站点,完全没必要在此浪费过多时间和精力。但是移动端的 M 站或许还需考虑跨浏览器兼容的问题,因为各种机型、系统和宿主环境的不同,可能会影响到我们站点的展示效果。

比如安卓和 IOS 的区别,刘海屏的兼容,系统自带浏览器与微信内置浏览器的区别等,仍是我们需要关注的点。

四. 测试框架/工具推荐

  1. Jest 测试框架

简介

Jest is a delightful JavaScript Testing Framework with a focus on simplicity.

It works with projects using: Babel[3], TypeScript[4], Node[5], React[6], Angular[7], Vue[8] and more!

Jest[9] is a JavaScript test runner that lets you access the DOM via [jsdom](https://reactjs.org/docs/testing-environments.html#mocking-a-rendering-surface "jsdom"). While jsdom is only an approximation of how the browser works, it is often good enough for testing React components. Jest provides a great iteration speed combined with powerful features like mocking modules[10] and timers[11] so you can have more control over how the code executes.

https://jestjs.io/

React 官方推荐的测试框架之一,很多公司的前端开发框架也都已经集成了其单测能力。使用教程实在是太多了,大家感兴趣可以去搜搜并在项目中尝试去启用它。

https://reactjs.org/docs/testing.html

适用场景

Jest 框架的功能非常齐全,基本上能覆盖大多数的前端测试场景,最常用的包括以下几个场景:

A. 函数单元测试

通常用来覆盖我们项目中的 Util 方法/函数,以保证函数代码健壮性;因为 Util 也通常都是纯函数,对于单元测试非常友好。

import sum from './sum';

it('sums numbers', () => {
  expect(sum(12)).toEqual(3);
  expect(sum(22)).toEqual(4);
});

B. React 组件测试

官方建议使用 Create React App[12],它已经内置包含了 可用的 Jest[13],我们只需要添加 react-test-renderer 来渲染快照即可。

组件测试技术的范围很广,它们的范围从验证组件渲染而不抛错的冒烟测试到浅层渲染和测试一些输出,再到完整渲染和测试组件生命周期和状态变化,因此如果要完整的对一个组件进行测试可能非常复杂。

一个简单的组件渲染冒烟测试,测试挂载一个组件,并确保它在渲染过程中没有抛出异常;

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

it('renders without crashing', () => {
  const div = document.createElement('div');
  ReactDOM.render(<App />, div);
});

浅层渲染测试

// 组件代码
function MyComponent() {
  return (
    <div>
      <span className="heading">Title</span>
      <Subcomponent foo="bar" />
    </div>

  );
}

// 测试代码
import ShallowRenderer from 'react-test-renderer/shallow';

const renderer = new ShallowRenderer();
renderer.render(<MyComponent />);
const result = renderer.getRenderOutput();

expect(result.type).toBe('div');
expect(result.props.children).toEqual([
  <span className="heading">Title</span>,
  <Subcomponent foo="bar" />
]);

C. 快照测试

快照测试即就是渲染 UI 组件之后,保存一个快照文件,之后每次修改都检测它是否与保存的快照文件相匹配。若两个快照不匹配,测试将失败:有可能做了意外的更改,或者 UI 组件已经更新到了新版本,需要我们及时更新已保存的快照。

import renderer from 'react-test-renderer';
import Link from '../Link';

it('renders correctly', () => {
  const tree = renderer
    .create(<Link page="http://www.facebook.com">Facebook</Link>)
    .toJSON();
  expect(tree).toMatchSnapshot();
});

首次执行后它会在指定目录生成一个快照文件,我们应该把它加入到代码仓库中一起提交并 Review,确保后续的更新都有一个稳定的快照用于比对。

exports[`renders correctly 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;
  1. 自动化测试套件

简介

是公司内部存在一个基于 Puppeteer 实现的产品 U,它是一个面向 Web 自动化测试的平台,适用于 Web E2E 测试场景,支持用例编写、实时预览、接入流程,提供详细的测试报告。目前并没有对外开放,但是大家可以使用原生的 Puppeteer 实现。

A. E2E 测试

可以在线的实现用例编写、调试、执行和发布的能力,非常方便。

B. 兼容性测试

可以通过引入一些通用的开源库进行浏览器的兼容测试:比如是否有未编译的 ES6+ , 是否存在未处理的 css 属性,甚至可以模拟低版本的浏览器。

C. 视觉回归测试

可以通过浏览器将页面进行截图,从而进行图片 Diff 比对实现对于 Web 站的视觉回归分析;

D. 性能测试

可以在无头浏览器中接入了 Web 性能测试中常用的工具 —— Lighthouse;我们可以通过暴露的 zAP I 很方便的进行性能相关指标的检测。

五. 对于前端测试的一些建议

  1. 从业务场景出发,

    无需照搬流程与模式

网上有无数的前端测试最佳实践,但我们没必要去照搬照学,因为它不见得是我们项目的最佳实践。

如何构建最适合我们项目的前端测试计划,或许你需要先思考这几个问题:

A. 决定前端测试的预算和投入

要实现前端测试,无疑要投入更多的研发人力和成本在其中,因为要支持单测导致研发工期翻倍是再正常不过的事情。因此到底能在项目中分出多少人力用于前端测试,是我们首先要考虑的问题。

如果项目处于初期,需要快速的功能迭代和打磨,研发人力已经非常紧缺了,这时投入去做前端测试显然是不合时宜的。如果项目比较稳定,且需要长期稳定的人力投入在回归和测试过程,那在前端测试投入精力会是个好选择;

B. 决定前端测试的覆盖范围

从前端测试相关类型介绍就可以知道,前端测试领域非常宽广,各式各样的场景都可以用自动化测试来覆盖,通常我们应该从项目的功能场景出发去思考。

如果项目是插件、工具函数、工具包,那么就适合单元测试;

如果项目是组件库,可以考虑结合单元测试和 E2E 测试去覆盖;

如果是承载大流量的官网、落地页,那么就非常适合 E2E 测试、视觉回归测试、可访问性测试、性能测试、兼容性测试等;

如果项目是中后台的管理系统,首先希望你已经思考了前一个问题,其次这个场景可能更适合单元测试和验收测试等;

  1. 先行动起来,

    不急着追求覆盖率

通常在测试计划制定时,都会拍脑袋说一个指标,比如要求代码覆盖度达到 80%以上。但对于大多数的 Web 前端测试的场景,追求一个非常高的覆盖度没有太多意义,因为我们大多数代码都是渲染逻辑和样式。

如果一定要求 80%,开发者无疑会认为这是浪费时间。到底多少合适,没有正确的答案,只能视情况而定。

所以归根到底,我们对前端代码中错误的风险的容忍度如何?如果某些非核心功能,修改问题所需的时间与编写额外测试的成本相比,到底哪个成本更高?

因此我的建议是没必要追求一个固定的覆盖率,我们可以选择核心的模块和功能来先进行测试的覆盖,又或者是逻辑分支较多比较复杂的逻辑,我们可以用单元测试驱动我们去开发。

总之在必要的时候先行动起来吧,我们可以暂时弱化目标;

  1. 以有测试为荣,

    但不以无测试为耻

我们应该重视前端测试,但没必要神话前端测试;

如果有充分的前端测试辅助,可以避免掉很多的人为错误和疏忽导致的线上事故,对于新人也能给予足够的保护和支持。我们应该鼓励和支持前端测试的建设,并以此为荣。

但如同上面预算和投入中讨论的,如果我们的项目仍处于频繁变更的时期,那我们应该花更多的时间去做产品业务的打磨,技术方案的讨论、代码规范与 CR 流程的建立、团队技术能力的培养,而不是投入大量成本在前端测试中。

  1. 避免破窗效应

以一幢有少许破窗的建筑为例,如果那些窗没修理好,可能将会有破坏者破坏更多的窗户。最终他们甚至会闯入建筑内,如果发现无人居住,也许就在那里占领、定居或者纵火。又或想像一条人行道有些许纸屑,如果无人清理,不久后就会有更多垃圾,最终人们会视为理所当然地将垃圾顺手丢弃在地上。

如果要推行前端测试,请和项目组的同学提前对齐计划和预期。明确测试的执行流程和准入机制,如果出现测试不通过的用例,需要及时排查问题而不是通过“跳过”功能豁免流程。

一旦部分用例长时间无人处置,最终结果就是越来越多的用例逐渐失效,最终我们的测试计划也会随之泡汤。

六. 写在最后

本文更偏向于科普性质,感兴趣的内容欢迎大家自行搜索了解和学习;希望通过本文你能对前端测试有一个基本的概念和了解,能在工作中培养独立思考的能力,打造出最适合我们项目的前端测试计划。

也希望大家把更多的精力放在方案阶段与开发过程中,并对每一次的上线变更抱有敬畏之心,不要期盼着通过自动化测试实现所有风险的拦截。

参考资料


[1]Jest: https://jestjs.io/

[2]Jasmine: https://jasmine.github.io/

[3]Babel: https://babeljs.io/

[4]TypeScript: https://www.typescriptlang.org/

[5]Node: https://nodejs.org/

[6]React: https://reactjs.org/

[7]Angular: https://angular.io/

[8]Vue: https://vuejs.org/

[9]Jest: https://facebook.github.io/jest/

[10]modules:

https://reactjs.org/docs/testing-environments.html#mocking-modules

[11]timers: 

https://reactjs.org/docs/testing-environments.html#mocking-timers

[12]Create React App: 

https://create-react-app.dev/

[13]可用的 Jest: 

https://create-react-app.dev/docs/running-tests/#docsNav

向下滑动查看

- EOF -


加主页君微信,不仅前端技能+1

主页君日常还会在个人微信分享前端开发学习资源技术文章精选,不定期分享一些有意思的活动岗位内推以及如何用技术做业余项目

加个微信,打开一扇窗



推荐阅读  点击标题可跳转

1、前端从 web 服务器或者 CDN 下载资源

2、超全面的前端工程化配置指南

3、我是如何带领团队从零到一建立前端规范的?


觉得本文对你有帮助?请分享给更多人

推荐关注「前端大全」,提升前端技能

点赞和在看就是最大的支持❤️

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存